/**JavaFile******************************************************************

  FileName    [Program that implements the Santa Claus Problem]

  Synopsis [ Santa Claus Problem template ]

  Author      [Iria Estevez-Ayres <ayres@it.uc3m.es>]

  Copyright   [Copyright (c) 2019 Carlos III University of Madrid
  All rights reserved.

  Permission is hereby granted, without written agreement and without license
  or royalty fees, to use, copy, modify, and distribute this software and its
  documentation for any purpose, provided that the above copyright notice and
  the following two paragraphs appear in all copies of this software.

  IN NO EVENT SHALL THE CARLOS III UNIVERSITY OF MADRID BE LIABLE TO ANY PARTY
  FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
  OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE CARLOS III
  UNIVERSITY OF MADRID HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

  THE CARLOS III UNIVERSITY OF MADRID SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN
  "AS IS" BASIS, AND CARLOS III UNIVERSITY OF MADRID HAS NO OBLIGATION TO
  PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.]

 ******************************************************************************/

import java.lang.*;
import java.util.*;

import java.util.concurrent.Semaphore;

class SantaFinal extends Thread{
    private Random rnd;
    private Semaphore santa, mutexRenos, llegadaRenos, trineoRenos, terminadoRenos, esperaElfos, dudasElfos;
    public SantaFinal(Semaphore santa, Semaphore mutexRenos, Semaphore llegadaRenos,
            Semaphore trineoRenos, Semaphore terminadoRenos, Semaphore esperaElfos, Semaphore dudasElfos) {
        this.santa = santa;
        this.mutexRenos = mutexRenos;
        this.llegadaRenos = llegadaRenos;
        this.trineoRenos = trineoRenos;
        this.terminadoRenos = terminadoRenos;
        this.esperaElfos = esperaElfos;
        this.dudasElfos = dudasElfos;
        rnd = new Random();
    }

    public void run(){
        while(true){
            try { santa.acquire(); } catch (InterruptedException e) { e.printStackTrace(); }
            try { mutexRenos.acquire(); } catch (InterruptedException e) { e.printStackTrace();	}
            if (ReindeerFinal.numRenos == 9){
                System.out.println("Santa has been woken up by reindeers...");
                //the reinders woke me up
                ReindeerFinal.numRenos = 0;
                mutexRenos.release();
                //I notify them
                System.out.println("All the reindeers have arrived. Santa notifies them that they are all ready");
                for (int i=0; i< 9; i++)
                    llegadaRenos.release();
                //I prepare the sleigh
                System.out.println("Santa has prepared the sleigh");
                for (int i=0; i< 9; i++)
                    trineoRenos.release();
                //toy delivering
                System.out.println("Toy delivering...");
                try { Thread.sleep(rnd.nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); }
                //work finished. The reindeers
                //can take a holiday
                System.out.println("Work finished. The reindeers can take a holiday");
                for (int i=0; i< 9; i++)
                    terminadoRenos.release();
            }//end if(numRenos)
            else{//del if (numRenos == 9)
                System.out.println("Santa has been woken up by elves...");
                mutexRenos.release();
                //It's the elves
                //I notify them
                for (int i=0; i< 3; i++)
                    esperaElfos.release();
                //I solve each doubt individually
                for (int i=0; i< 3; i++){
                    try { Thread.sleep(rnd.nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace();}
                    dudasElfos.release();
                }
            }//end else
        }//end while(true)

    }
}

class ElveFinal extends Thread{
    public static int numElfos;
    private Semaphore santa, mutexElfos, capElfos, esperaElfos, dudasElfos;

    public ElveFinal(Semaphore santa, Semaphore mutexElfos, Semaphore capElfos,
            Semaphore esperaElfos, Semaphore dudasElfos) {
        this.santa = santa;
        this.mutexElfos = mutexElfos;
        this.capElfos = capElfos;
        this.esperaElfos = esperaElfos;
        this.dudasElfos = dudasElfos;
    }

    public void run(){
        // working
        try { capElfos.acquire(); } catch (InterruptedException e) {e.printStackTrace();} //ask for a place
        try { mutexElfos.acquire(); } catch (InterruptedException e) { e.printStackTrace();	}
        numElfos++;
        System.out.println("Entering elf. Number of elves = " + numElfos);
        if (numElfos == 3) {
            System.out.println("There are 3 elves, so Santa can we woken up. Waiting room door is now closed.");
            santa.release();
        }
        mutexElfos.release();
        // waiting for the other 2
        try { esperaElfos.acquire(); } catch (InterruptedException e) { e.printStackTrace(); }
        // waiting until my doubt is solved
        try { dudasElfos.acquire(); } catch (InterruptedException e) { e.printStackTrace();	}
        try { mutexElfos.acquire();	} catch (InterruptedException e) { e.printStackTrace();	}
        System.out.println("Exiting elf. Number of elves = " + numElfos);
        numElfos--;
        if (numElfos == 0){ //last elf
                            //opens the waiting room for the next
            System.out.println("All elves have left, so the door is open so that other elves can enter");
            for (int i=0; i< 3; i++)
                capElfos.release();
        }
        mutexElfos.release();
    }

}

class ReindeerFinal extends Thread{
    public static int numRenos = 0;

    private Semaphore santa, mutexRenos, llegadaRenos, trineoRenos, terminadoRenos;
    public ReindeerFinal(Semaphore santa, Semaphore mutexRenos, Semaphore llegadaRenos,
            Semaphore trineoRenos, Semaphore terminadoRenos) {
        this.santa = santa;
        this.mutexRenos = mutexRenos;
        this.llegadaRenos = llegadaRenos;
        this.trineoRenos = trineoRenos;
        this.terminadoRenos = terminadoRenos;
    }

    public void run(){
        while(true){
            // holidays
            try { mutexRenos.acquire(); } catch (InterruptedException e) {e.printStackTrace();}
            numRenos++;
            System.out.println("Entering reindeer. Number of reindeers = " + numRenos);
            if (numRenos == 9) {
                System.out.println("There are 9 reindeers, so Santa can we woken up");
                santa.release();
            }
            mutexRenos.release();
            try { llegadaRenos.acquire(); } catch (InterruptedException e) {e.printStackTrace();}
            // all the reindeers have arrived
            try { trineoRenos.acquire(); } catch (InterruptedException e) {e.printStackTrace();}
            // toy delivering
            try { terminadoRenos.acquire(); } catch (InterruptedException e) {e.printStackTrace();}
        }
    }

}

public class NorthPoleFinal{

    public static void main(String[] args){

        // You can (and should) create new constructors for each class
        // So, you will also need to modify this main

        Semaphore santaSem = new Semaphore(0);
        Semaphore mutexRenos = new Semaphore(1);
        Semaphore llegadaRenos = new Semaphore(0);
        Semaphore trineoRenos = new Semaphore(0);
        Semaphore terminadoRenos = new Semaphore(0);

        Semaphore mutexElfos = new Semaphore(1);
        Semaphore capElfos = new Semaphore(3);
        Semaphore esperaElfos = new Semaphore(0);
        Semaphore dudasElfos = new Semaphore(0);

        SantaFinal claus = new SantaFinal(santaSem, mutexRenos, llegadaRenos, trineoRenos, terminadoRenos, esperaElfos, dudasElfos);
        ReindeerFinal rudolf;
        ElveFinal legolas;

        claus.start();

        for (int i=0; i < 9; i++){
            rudolf = new ReindeerFinal(santaSem, mutexRenos, llegadaRenos, trineoRenos, terminadoRenos);
            rudolf.start();
        }

        for (int i=0; i < 100; i++){
            legolas = new ElveFinal(santaSem, mutexElfos, capElfos, esperaElfos, dudasElfos);
            legolas.start();
        }

    }
}
